/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package phd.dupenois.changeidentifiers.optimisation.singlepattern;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import phd.dupenois.changeidentifiers.ChangeIdentifier;
import phd.dupenois.changeidentifiers.ChangeIdentifierListener;
import phd.dupenois.changeidentifiers.ChangeIdentifierSet;
import phd.dupenois.changeidentifiers.dotpatterns.DotPattern;
import phd.dupenois.changeidentifiers.engine.ChangeIdentifierApplication;
import phd.dupenois.changeidentifiers.engine.TerminatingRunEngine;
import phd.dupenois.changeidentifiers.footprints.Footprint;
import phd.dupenois.changeidentifiers.footprints.algorithms.FootprintAlgorithm;
import phd.dupenois.changeidentifiers.optimisation.graphs.OptimisationGraph;
import phd.dupenois.changeidentifiers.optimisation.singlepattern.frames.GraphFrame;
import phd.dupenois.changeidentifiers.optimisation.singlepattern.frames.ResultSaveInterface;
import phd.dupenois.changeidentifiers.optimisation.singlepattern.frames.SettingsDialog;
import phd.dupenois.es.nplusn.NplusNListener;

/**
 *
 * @author mpd209
 */
public class Application extends ChangeIdentifierApplication implements Runnable, 
        ChangeIdentifierListener, NplusNListener<ChangeIdentifierSolution>,
        ResultSaveInterface{


    private class ArgsReaderValues{
        private final static String QUIET = "quiet";
        private final static String HELP = "help";
        private final static String DOT_PATTERN_PATH = "dotPatternPath";
        private final static String ALGORITHM_PATH = "algorithmPath";
        private final static String ALGORITHM_CLASS = "algorithmClass";
        private final static String GENERATIONS = "generations";
        private final static String POPULATION = "population";
        private final static String RESULTS_DIR = "resultsDirectory";
    }

    private OptimisationGraph graph;
    private GraphFrame frame;
    private SinglePattern es;
    private ChangeIdentifierSolution[] finishedArchive;
    private FootprintAlgorithm algorithm;
    private double dotPatternAverageSize;
    private int dotPatternMaxSize=-1;
    private int dotPatternMinSize=Integer.MAX_VALUE;
    private int dotPatternStreamLength;
    private String firstDotPatternLocation;


    private File resultsDirectory;
//    private File resultsSheet;
    
    /**
     *
     */
     private boolean quiet;

    private static int GENERATIONS = 250;
    private static int POPULATION = 10;
//    private ArgsReader argsReader;

    /**
     *
     * @param reader 
     */

    public Application(ArgsReader reader){
        super(true);
    /**
     *
     */
        String[] dotPatternLocations = new String[]{};
        Class<? extends FootprintAlgorithm> algorithmClass = null;
        this.quiet = false;
        this.graph = new OptimisationGraph();
        if(!reader.hasFlag(ArgsReaderValues.QUIET)){
            try {
                UIManager.setLookAndFeel(
                    UIManager.getSystemLookAndFeelClassName());
            }catch (UnsupportedLookAndFeelException e) {
            }catch (ClassNotFoundException e) {
            }catch (InstantiationException e) {
            }catch (IllegalAccessException e) {
            }
            this.frame = new GraphFrame(graph, this);
            this.frame.setMaxGeneration(GENERATIONS);
            this.frame.setVisible(true);
            SettingsDialog settingsDialog = new SettingsDialog(this.frame, true);
            settingsDialog.setVisible(true);
            dotPatternLocations = settingsDialog.getDotPatternStream();
            if(dotPatternLocations.length==0){
                System.exit(0);
            }
            algorithmClass = settingsDialog.getAlgorithmClass();
        }else{
            this.quiet = true;
            String dotPatternPath = reader.getString(ArgsReaderValues.DOT_PATTERN_PATH);
            String algorithmPath = reader.getString(ArgsReaderValues.ALGORITHM_PATH);
            String algorithmClassName = reader.getString(ArgsReaderValues.ALGORITHM_CLASS);
            this.resultsDirectory = new File(reader.getString(ArgsReaderValues.RESULTS_DIR));
            if(!this.resultsDirectory.isDirectory()){
                if(!this.resultsDirectory.mkdirs()){
                    System.out.println("Cannot create results directory: "+reader.getString(ArgsReaderValues.RESULTS_DIR));
                    System.exit(1);
                }
            }
            try {
                GENERATIONS = reader.getInt(ArgsReaderValues.GENERATIONS);
            } catch (NumberFormatException e) {
                System.out.println("Generations number: '"+reader.getString(ArgsReaderValues.GENERATIONS)+"' is not a valid number");
                System.exit(1);
            }
            try {
                POPULATION = reader.getInt(ArgsReaderValues.POPULATION);
            } catch (NumberFormatException e) {
                System.out.println("Population number: '"+reader.getString(ArgsReaderValues.POPULATION)+"' is not a valid number");
                System.exit(1);
            }

            File dotPatternDir = new File(dotPatternPath);
            if(dotPatternDir.isDirectory()){
                File[] patternsFiles = dotPatternDir.listFiles(new FilenameFilter() {
                    public boolean accept(File dir, String filename) {
                        return (filename.matches("^.*\\.[cC][sS][vV]$"));
                    }
                });
                dotPatternLocations = new String[patternsFiles.length];
                for(int i=0;i<patternsFiles.length;i++){
                    dotPatternLocations[i] = patternsFiles[i].getAbsolutePath();
                }
            }else{
                System.out.println("'"+dotPatternPath+"' is not a directory");
                System.exit(1);
            }

            String urlPath = "jar:file:///" + algorithmPath + "!/";
            try {
                URLClassLoader classLoader = new URLClassLoader(new URL[]{new URL(urlPath)});
                algorithmClass = Application.getFootprintAlgorithmClass(classLoader, algorithmClassName);
            } catch (MalformedURLException malformedURLException) {
                System.out.println("'"+urlPath+"' is not a valid url");
                System.exit(1);
            } catch (ClassNotFoundException classNotFoundException) {
                System.out.println("'"+algorithmClassName+"' cannot be found in given jar");
                System.exit(1);
            }

        }
        this.dotPatternStreamLength = dotPatternLocations.length;
        Vector<DotPattern> patterns = new Vector<DotPattern>();
        int size;
        long totalDots = 0;
        for (String location : dotPatternLocations) {
            if(firstDotPatternLocation==null){
                firstDotPatternLocation = location;
            }
            DotPattern dp = new DotPattern();
            try {
                dp.load(location);
                patterns.add(dp);
            } catch (FileNotFoundException ex) {
                exceptionThrown(ex);
                System.exit(1);
            } catch (IOException ex) {
                exceptionThrown(ex);
                System.exit(1);
            }
            size = dp.size();
            if(size<this.dotPatternMinSize) this.dotPatternMinSize = size;
            if(size>this.dotPatternMaxSize) this.dotPatternMaxSize = size;
            totalDots +=size;
        }
        this.dotPatternAverageSize = totalDots/this.dotPatternStreamLength;
        algorithm = null;
        try {
             algorithm = algorithmClass.newInstance();
        } catch (InstantiationException ex) {
            exceptionThrown(ex);
            System.exit(1);
        } catch (IllegalAccessException ex) {
            exceptionThrown(ex);
            System.exit(1);
        }
        System.out.println("Starting");
        double baselineTime = ((TerminatingRunEngine)this.engine).getBaselineTimeTaken(algorithm,
                patterns.toArray(new DotPattern[patterns.size()]));
        ChangeIdentifierSolution.setTimeTakenBaseline(baselineTime);
        System.out.println("Got Baseline");
        this.graph.addBaseLine(false, baselineTime);
        this.es = new SinglePattern((TerminatingRunEngine)this.engine,
                GENERATIONS, POPULATION, this, patterns, algorithm);
     

    }
    
    /**
     *
     */
    public void run() {
        new Thread(this.es).start();
    }
    

    public static Class<? extends FootprintAlgorithm> getFootprintAlgorithmClass(
            URLClassLoader classLoader, String classname) throws ClassNotFoundException {
        Class footprintClass = classLoader.loadClass(classname);
        if (FootprintAlgorithm.class.isAssignableFrom(footprintClass)) {
            return (Class<? extends FootprintAlgorithm>) footprintClass;
        } else {
            return null;
        }
    }

    /**
     *
     * @param args
     */
    public static void main(String[] args){
        ArgsReader reader = new ArgsReader();
        reader.addFlag("-h",        ArgsReaderValues.HELP, "Prints argument descriptions");
        reader.addFlag("--help",    ArgsReaderValues.HELP);
        reader.addFlag("-q",        ArgsReaderValues.QUIET, "Whether or not to use the graphical display");
        reader.addFlag("--quiet",   ArgsReaderValues.QUIET);
        reader.addProperty("-d",    ArgsReaderValues.DOT_PATTERN_PATH, "Path of dot pattern folder to use");
        reader.addProperty("-ap",   ArgsReaderValues.ALGORITHM_PATH, "Path to algorithm jar");
        reader.addProperty("-a",    ArgsReaderValues.ALGORITHM_CLASS, "Class path of algorithm to use");
        reader.addProperty("-g",    ArgsReaderValues.GENERATIONS, "Number of generations");
        reader.addProperty("-p",    ArgsReaderValues.POPULATION, "Population Size");
        reader.addProperty("-r",    ArgsReaderValues.RESULTS_DIR, "Directory to store results");
        try {
            reader.loadArgs(args);
        } catch (IllegalArgumentException illegalArgumentException) {
            System.out.println(illegalArgumentException.getMessage());
            System.exit(1);
        }
        if(reader.hasFlag(ArgsReaderValues.HELP)){
            System.out.println(reader.helpText());
            System.exit(0);
        }
        if(reader.hasFlag(ArgsReaderValues.QUIET)){
            String error = null;
            if(!reader.hasProperty(ArgsReaderValues.DOT_PATTERN_PATH)){
                error = "Required property: '"+
                        ArgsReaderValues.DOT_PATTERN_PATH+"' not found.";
            }
            if(!reader.hasProperty(ArgsReaderValues.ALGORITHM_PATH)){
                error = (error!=null?error+"\n":"")+"Required property: '"+
                        ArgsReaderValues.ALGORITHM_PATH+"' not found.";
            }
            if(!reader.hasProperty(ArgsReaderValues.ALGORITHM_CLASS)){
                error = (error!=null?error+"\n":"")+"Required property: '"+
                        ArgsReaderValues.ALGORITHM_CLASS+"' not found.";
            }
            if(!reader.hasProperty(ArgsReaderValues.GENERATIONS)){
                error = (error!=null?error+"\n":"")+"Required property: '"+
                        ArgsReaderValues.GENERATIONS+"' not found.";
            }
            if(!reader.hasProperty(ArgsReaderValues.POPULATION)){
                error = (error!=null?error+"\n":"")+"Required property: '"+
                        ArgsReaderValues.POPULATION+"' not found.";
            }
            if(!reader.hasProperty(ArgsReaderValues.RESULTS_DIR)){
                error = (error!=null?error+"\n":"")+"Required property: '"+
                        ArgsReaderValues.RESULTS_DIR+"' not found.";
            }
            if(error != null){
                System.out.println(error);
                System.exit(1);
            }
        }
        new Thread(new Application(reader)).start();
    }



    /*****************************
     Frame Calls
    ******************************/



    /**
     * @param folderLocation
     * @return true if results were saved
     * @throws IOException
     */
    public boolean saveResults(File folderLocation) throws IOException{
        if(this.finishedArchive==null||this.finishedArchive.length<1)return false;
        if(!folderLocation.isDirectory()){
            boolean makeDirs = folderLocation.mkdirs();
            if(!makeDirs){
                throw new IOException("Failed to make results directory "+folderLocation.getCanonicalPath());
            }
        }
        File set;
        String filename;
        if(!this.quiet){
            for (ChangeIdentifierSolution solution : this.finishedArchive) {
                filename = "CIS-t[" + solution.getTimeTaken() + "]-e[" + solution.getSymmetricError() + "].cis";
                set = new File(folderLocation.getCanonicalPath() + "/" + filename);
                solution.getChangeIdentifierSet().saveToFile(set);
            }
        }
        File summaryFile = new File(folderLocation.getCanonicalPath()+"/summary.properties");
        BufferedWriter writer = new BufferedWriter(new FileWriter(summaryFile));
        writer.append("footprintAlgorithm="+this.algorithm.getClass().getCanonicalName()+"\n");
        writer.append("dotPatternAverageSize="+dotPatternAverageSize+"\n");
        writer.append("dotPatternMaxSize="+dotPatternMaxSize+"\n");
        writer.append("dotPatternMinSize="+dotPatternMinSize+"\n");
        writer.append("dotPatternStreamLength="+dotPatternStreamLength+"\n");
        writer.append("firstDotPatternLocation="+firstDotPatternLocation);
        writer.close();

        if(useGraph()){
            File graphFile = new File(folderLocation.getCanonicalPath()+"/graph.png");
            this.graph.writeImage(graphFile, "png");
        }
        return true;
    }

    private boolean useGraph(){
        return !this.quiet;
    }

    /*****************************
     ES Events
     *******************************/

    /**
     * Called when the es starts
     */
    public void onStart() {}


    /**
     *
     * @param solution
     * @param atGeneration
     */
    public void onSolutionAdded(ChangeIdentifierSolution solution, int atGeneration) {
        if(useGraph()){
            this.graph.addPoint(solution.getTimeTaken(), solution.getSymmetricError(),
                    solution.getChangeIdentifierSet());
        }
    }

    /**
     *
     * @param solution
     * @param atGeneration
     */
    public void onSolutionRemoved(ChangeIdentifierSolution solution, int atGeneration) {

        if(useGraph()){
            this.graph.removePoint(solution.getChangeIdentifierSet());
        }
    }

    /**
     *
     * @param mutant
     * @param atGeneration
     */
    public void onMutantGenerated(ChangeIdentifierSolution mutant, int atGeneration) {
    }

    /**
     *
     * @param generationEnded
     */
    public void onGenerationEnd(int generationEnded) {
        if(!this.quiet){
            this.frame.incrementGeneration();
        }else{
//            System.out.println("Generation Ended: "+generationEnded);
            try {
//                if (resultsSheet == null) {
//                    System.out.println("Results Directory: "+resultsDirectory);
//                    resultsSheet = new File(resultsDirectory.getAbsolutePath() + "/results["+generationEnded+"].csv");
//                    resultsSheet.createNewFile();
//                }
                String genString = "" + generationEnded;
                int indexLength = (genString).length();
                StringBuffer zeroes = new StringBuffer();
                int maxLength = (""+this.GENERATIONS).length();
                for (int i = indexLength; i < maxLength; i++) {
                    zeroes.append("0");
                }
                String generationFolderLoc = resultsDirectory.getAbsolutePath()+"/gen["+zeroes+genString+"]";
                File generationFolder = new File(generationFolderLoc);
                generationFolder.mkdirs();
                File resultsSheet = new File(generationFolderLoc + "/results.csv");
                resultsSheet.createNewFile();
                File set;
                String cisFilename;
                BufferedWriter resultsWriter = new BufferedWriter(new FileWriter(resultsSheet));
                Vector<ChangeIdentifierSolution> archiveClone = (Vector<ChangeIdentifierSolution>)this.es.getArchive().clone();
                for (ChangeIdentifierSolution solution : archiveClone) {
                    cisFilename = "CIS-t[" + solution.getTimeTaken() + "]-e[" + solution.getSymmetricError() + "].cis";
                    set = new File(generationFolderLoc + "/" + cisFilename);
                    solution.getChangeIdentifierSet().saveToFile(set);
                    resultsWriter.append(solution.getSymmetricError() + ", " + solution.getTimeTaken() + ", \"" + cisFilename + "\"\n");
                }
                resultsWriter.close();
            } catch (IOException iOException) {
                iOException.printStackTrace();
            }
        }
    }

    /**
     *
     * @param generationStarting
     */
    public void onGenerationStart(int generationStarting) {
    }
    /**
     *
     * @param archive
     */
    public void onFinish(ChangeIdentifierSolution[] archive) {
        Vector<OptimisationGraph.SetDetails> setDetails = new Vector<OptimisationGraph.SetDetails>();
        ChangeIdentifierSet set;
        for(ChangeIdentifierSolution solution : archive){
            set = solution.getChangeIdentifierSet();
            System.out.println("Finish Set "+set.getName()+" "+set.getVersion()+" Took "+
                    solution.getTimeTaken()+"ms for an error of "+solution.getSymmetricError());
            System.out.println(set.toXMLString());
            System.out.println("---------------");
            setDetails.add(new OptimisationGraph.SetDetails(set,solution.getTimeTaken(),solution.getSymmetricError()));
        }
        if(useGraph()){
            graph.setFinishedSet(setDetails.toArray(new OptimisationGraph.SetDetails[setDetails.size()]));
        }
        this.finishedArchive = archive;
        if(this.quiet){
            try {
                saveResults(this.resultsDirectory);
            } catch (IOException ex) {
                System.out.println("Failed to save results "+ex.getMessage());
            }
        }
    }
    /**
     *
     */
    public void onFinish() {
        System.out.println("Finished with an empty archive");
    }

    /*****************************
      Change Identifier Events
     *******************************/

    /**
     *
     * @param ex
     */
    public void exceptionThrown(Exception ex) {
        int lineNo = ex.getStackTrace()[0].getLineNumber();
        System.out.println("[CI EXCEPTION-"+lineNo+"] " + ex.getClass().getSimpleName() + " :: " + ex.getMessage());
        ex.printStackTrace();
    }

    /**
     *
     */
    public void dotPatternBufferIsEmpty() {}

    /**
     *
     * @param dotPattern
     */
    public void drawDotPattern(DotPattern dotPattern) {}

    /**
     *
     * @param footprint
     */
    public void drawFootprint(Footprint footprint) {}

    /**
     *
     * @param time
     */
    public void startedEngine(long time) {}

    /**
     *
     * @param timeTaken
     * @param redrawCount
     */
    public void finishedWithChangeIdentifiers(long timeTaken, int redrawCount) {}

    /**
     *
     */
    public void finishedEngine() {}

    /**
     *
     */
    public void startedRecordingTests() {}

    /**
     *
     */
    public void finishedRecordingTests() {}

    /**
     *
     * @param time
     */
    public void startedRunningTestsWithoutChangeIdentifiers(long time) {}

    /**
     *
     * @param timeTaken
     * @param redrawCount
     * @param difference
     * @param redrawCountDifference
     */
    public void finishedRunningTestsWithoutChangeIdentifiers(long timeTaken, int redrawCount, long difference, int redrawCountDifference) {}

    /**
     *
     * @param time
     */
    public void startedChangeIdentifierSet(long time) {}

    /**
     *
     * @param timeTaken
     */
    public void finishedChangeIdentifierSet(long timeTaken) {}

    /**
     *
     * @param identifier
     * @param maxFails
     */
    public void changeIdentifierBrokenMaxFail(ChangeIdentifier identifier, int maxFails) {}

    /**
     *
     * @param identifier
     * @param threshold
     * @param value
     * @param redraw
     */
    public void changeIdentifierBrokenOwnThreshold(ChangeIdentifier identifier, double threshold, double value, boolean redraw) {}

    


}
